/* * Copyright (c) 2010, The University of Sheffield. * * This file is part of the GATE/Groovy integration layer, and is free * software, released under the terms of the GNU Lesser General Public * Licence, version 2.1 (or any later version). A copy of this licence * is provided in the file LICENCE in the distribution. * * Groovy is developed by The Codehaus, details are available from * http://groovy.codehaus.org */ package gate.groovy; import gate.Gate; import gate.GateConstants; import gate.Resource; import gate.creole.AbstractResource; import gate.creole.ResourceInstantiationException; import gate.creole.metadata.AutoInstance; import gate.creole.metadata.CreoleResource; import gate.gui.ActionsPublisher; import gate.gui.MainFrame; import gate.persist.PersistenceException; import gate.util.GateException; import gate.util.GateRuntimeException; import gate.util.persistence.PersistenceManager; import groovy.lang.Binding; import groovy.lang.GroovyShell; import groovy.lang.ReadOnlyPropertyException; import java.awt.event.ActionEvent; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.swing.AbstractAction; import javax.swing.Action; import org.codehaus.groovy.runtime.DefaultGroovyMethods; /** * Tool resource that sets up Groovy support for GATE. When the Groovy * plugin is loaded an instance of this resource is created, which (a) * adds a menu item to GATE Developer to run the Groovy console and (b) * mixes in the static methods of {@link gate.Utils} and * {@link gate.groovy.GateGroovyMethods} so they can be used as instance * methods by any Groovy code that runs after the plugin is loaded. */ @CreoleResource(name = "Groovy support for GATE", isPrivate = true, tool = true, autoinstances = @AutoInstance) public class GroovySupport extends AbstractResource implements ActionsPublisher { private static final long serialVersionUID = 4763983982544551334L; /** * Standard list of import statements that are available to any groovy script * or console in GATE. */ public static final String STANDARD_IMPORTS = "import gate.*;\n" + //"import gate.jape.*;\n" + //"import gate.creole.ontology.*;\n" + //"import gate.annotation.*;\n" + "import gate.util.*;\n"; public Resource init() throws ResourceInstantiationException { // mix-in gate.Utils and gate.groovy.GateGroovyMethods mixinGlobally(gate.Utils.class); mixinGlobally(GateGroovyMethods.class); // register the ScriptableController with the persistence mechanism try { PersistenceManager.registerPersistentEquivalent(ScriptableController.class, ScriptableControllerPersistence.class); } catch(PersistenceException e) { throw new ResourceInstantiationException(e); } return this; } /** * Mix all the static methods of the given class into their * respective types. * @param classToMix a category class. */ protected void mixinGlobally(Class<?> classToMix) { // find the set of types into which it needs to be mixed. // this means the types of the first argument of each // static method in the class. Set<Class<?>> typesToMixInto = new HashSet<Class<?>>(); for(Method method : classToMix.getMethods()) { if(Modifier.isStatic(method.getModifiers()) && method.getDeclaringClass() == classToMix && method.getParameterTypes().length > 0) { typesToMixInto.add(method.getParameterTypes()[0]); } } for(Class<?> clazz : typesToMixInto) { DefaultGroovyMethods.mixin(clazz, classToMix); } } private List<Action> actions; public List<Action> getActions() { if(actions == null) { actions = new ArrayList<Action>(); actions.add(new AbstractAction("Groovy Console", MainFrame.getIcon("groovyConsole")) { { putValue(SHORT_DESCRIPTION, "Console for Groovy scripting"); putValue(GateConstants.MENU_PATH_KEY, new String[] {"Groovy Tools"}); } private static final long serialVersionUID = 1L; public void actionPerformed(ActionEvent evt) { groovy.ui.Console console = new groovy.ui.Console(GroovySupport.class.getClassLoader()) { /** * Overridden to (a) install a Binding that knows about the * corpora, docs, prs and apps variables, and (b) set up the * standard imports each time a script is run. */ @Override public void newScript(ClassLoader parent, Binding binding) { // TODO Auto-generated method stub Binding realBinding = new GateBinding(binding); setShell(new GroovyShell(parent, realBinding) { public Object run(final String scriptText, final String fileName, String[] args) { return super.run(scriptText + "\n\n\n" + STANDARD_IMPORTS, fileName, args); } }); } /** * This is a workaround for a strange behaviour in Groovy. At some * point the superclass attempts to do "scriptRunning = false" from * inside a closure which is defined in a method of the Console * class. But rather than treating that as an assignment to the * private field scriptRunning in that class, Groovy treats this * as a setProperty, which fails when this object is a subclass * of Console. So we have to implement the property setter here, * delegating to the real private field in the superclass. */ @SuppressWarnings("unused") public void setScriptRunning(boolean b) { try { Field f = groovy.ui.Console.class.getDeclaredField("scriptRunning"); boolean a = f.isAccessible(); f.setAccessible(true); f.set(this, b); f.setAccessible(a); } catch(Exception e) { // give up } } }; console.run(); } }); } return actions; } /** * Special Binding subclass that handles requests for the variables * corpora, docs, prs and apps by delegating to the creole register. */ static class GateBinding extends Binding { GateBinding(Binding delegateBinding) { super(delegateBinding.getVariables()); } /** * Overridden to intercept requests for the 'pseudo-variables' * corpora, docs, prs and apps and direct them to the relevant * methods of the creole register. */ public Object getVariable(String name) { if("corpora".equals(name)) { try { return Gate.getCreoleRegister().getAllInstances( "gate.Corpus"); } catch(GateException e) { throw new GateRuntimeException(e); } } else if("docs".equals(name)) { return Gate.getCreoleRegister() .getLrInstances("gate.corpora.DocumentImpl"); } else if("prs".equals(name)) { return Gate.getCreoleRegister().getPrInstances(); } else if("apps".equals(name)) { try { return Gate.getCreoleRegister().getAllInstances( "gate.creole.AbstractController"); } catch(GateException e) { throw new GateRuntimeException(e); } } else { return super.getVariable(name); } } /** * Overridden to enforce read-only access to the 'pseudo-variables' * corpora, docs, prs and apps. * @throws ReadOnlyPropertyException if an attempt is made to set * any of these variables. */ public void setVariable(String name, Object value) { if("corpora".equals(name) || "docs".equals(name) || "prs".equals(name) || "apps".equals(name)) { throw new ReadOnlyPropertyException(name, this.getClass()); } super.setVariable(name, value); } } }